#####################################################################
# File:         GenericContainer.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2002, Faulkner Ltd.
# Author:       Brian Oliver
#
# Description:  This file defines the abstract GenericContainer class,
#               the base class of all container components used in
#               the Faulkner Knowledge Engine
#
# See Also:     GenericObject, ComponentContainer
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Common import *
from KnowledgeEngineCore import *
from GenericObject import GenericObject, GenericObjectInfo

#####################################################################
## Zope Library Imports
from App.Dialogs import MessageDialog
from OFS.ObjectManager import ObjectManager
from OFS.CopySupport import CopyError
from Globals import DTMLFile
from Acquisition import aq_base, aq_inner, aq_parent

#####################################################################
## Python Library Imports
import sys

#####################################################################
## GenericContainerInfo Class Definition
class GenericContainerInfo(GenericObjectInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return 'GenericContainer'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "Generic Container"

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            ABSTRACT: Return the identity type.

            This is a short string used for prefixing object identities
        """

        return "genericcontainer"

    #---------------------------------------------------------------#

    def isInstanceOf(self, className):
        """
            Returns if the class is an instance of the specified
            className, or implements the interface specified
            by the className
        """

        return className == 'GenericContainer' or GenericObjectInfo.isInstanceOf(self, className)
        
    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in 
            the specified container (containerInstance)
            
            NOTE: This is from this class' instance perspective.
        """

        #DEFAULT RULE: GenericObjects are permitted in GenericContainers
        return containerClassInfo.isInstanceOf('GenericObject')

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """

        #DEFAULT RULE: GenericContainers may have GenericObject children
        return childClassInfo.isInstanceOf('GenericObject')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        GenericObjectInfo.onConfigureProperties(self)
    
    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
        

        #initialise the super-class events for reflection
        GenericObjectInfo.onConfigureEvents(self)

    #---------------------------------------------------------------#

#####################################################################
## GenericContainer Class Definition
class GenericContainer(ObjectManager, GenericObject):
    """
        Base class for all container Faulkner Knowledge Engine components
    """

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = GenericContainerInfo()

    #---------------------------------------------------------------#

    # Custom zope manage_main for containers (to display contents by Order attribute)
    manage_main = DTMLFile('dtml/main', globals())

    #---------------------------------------------------------------#

    # The types allowable as child components
    all_meta_types = (
    )

    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the object"

        GenericObject.__init__(self, id, identity)

    #---------------------------------------------------------------#

    def __setstate__(self, state):
        """
            Initialise the state of the Object
            (does backward compatibility)
        """

        #perform default persistent actions
        GenericObject.__setstate__(self, state)
           
    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """

        #perform GenericObject instanciation
        GenericObject.onAfterInstanciation(self)

        #add the Contents tab to the front of the tab order for managing the container
        self.addManagementTab('Contents', 'manage_main', ('OFSP', 'ObjectManager_Contents.stx'), true)

    #---------------------------------------------------------------#

    def getDescendants(self, lResult = {}):
        """
            Returns the descendants of the container as a
            dictionary, indexed by GenericObject.getIdentity()
        """

        for lObject in self.objectValues():

            #is the object derived from GenericObject?
            if isinstance(lObject, GenericObject):

                #add the current object to the result
                lResult[lObject.getIdentity()] = lObject

                #is the current object composite?
                if lObject.isInstanceOf('GenericContainer'):

                    #add the object descendants
                    lResult.update(lObject.getDescendants())

        return lResult

    #---------------------------------------------------------------#

    def getChildren(self):
        """
            Returns a sequence of the immediate child objects
            (that are not Events) but any other zope object is permitted.
        """

        result = []
        for child in self.objectValues():
            if (hasattr(child,"isInstanceOf") and not child.isInstanceOf("Event")) or not hasattr(child,"isInstanceOf"):
                result = result + [child]

        return result

    #---------------------------------------------------------------#

    def getContents(self):
        """
            Returns a list of (child.getId(), child) for use
            by the 'Contents' tab (main.dtml)
        """

        return [(child.getId(), child) for child in self.getChildren()]

    #---------------------------------------------------------------#

    def getChildrenByInstanceOf(self, className):
        """
            Returns a sequence of the immediate child objects
            that are of the specified instance type (using
            GenericObject.isInstanceOf() method)
        """

        result = []
        for child in self.objectValues():
            if hasattr(child,"isInstanceOf") and child.isInstanceOf(className):
                result = result + [child]

        return result

    #---------------------------------------------------------------#

    def onAfterClone(self, identityMappings = {}):
        "Handle Knowledge Engine specific actions after this object has been cloned"

        GenericObject.onAfterClone(self, identityMappings)

        ###print "DEBUG: GenericContainer.onAfterClone: Completed"

    #---------------------------------------------------------------#

    def onAfterAdd(self):
        "Handle Knowledge Engine specific actions after this object has been added to a container"

        GenericObject.onAfterAdd(self)

        ###print "DEBUG: GenericContainer.onAfterAdd: Completed"

    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        "Handle Knowledge Engine specific actions before this object is deleted"

        GenericObject.onBeforeDelete(self)

        ###print "DEBUG: GenericContainer.onBeforeDelete: Completed"

    #---------------------------------------------------------------#

    def resolveIdentities(self, identityMappings = {}):
        """
            Attempt to resolve all identity references
            for this object and its decendants using the
            provided identityMappings dictionary
        """

        #handle changing identities for this object
        self.onAfterIdentityChange(identityMappings)

        #resolve decendants
        for object in self.objectValues():
            if isinstance(object, GenericObject):
                object.resolveIdentities(identityMappings)

    #---------------------------------------------------------------#

    def manage_afterClone(self, item):
        "FINAL: Override the standard Zope afterClone management"

        #do the usual Zope processing
        ObjectManager.manage_afterClone(self, item)

        #perform the GenericObject after clone processing
        GenericObject.manage_afterClone(self, item)

    #---------------------------------------------------------------#

    def manage_afterAdd(self, item, container):
        "FINAL: Override the standard Zope afterAdd management"

        #do the usual Zope processing
        ObjectManager.manage_afterAdd(self, item, container)

        #handle the knowledge engine specific updates
        self.onAfterAdd()

    #---------------------------------------------------------------#

    def manage_beforeDelete(self, item, container):
        "FINAL: Override the standard Zope beforeDelete management"

        #do the usual Zope processing
        ObjectManager.manage_beforeDelete(self, item, container)

        #handle the knowledge engine specific updates
        self.onBeforeDelete()

    #---------------------------------------------------------------#

    def containsChildren(self):
        """
            Return if the container contains children
        """

        return len(self.getChildren()) > 0

    #---------------------------------------------------------------#

    def getPermittedChildren(self, sortDisplayNames = false):
        """
            Return a list of ClassInfo instances for children that
            are permitted in this container
        """

        #use the KnowledgeEngineCore to determine this
        return getPermittedChildren(self.getClassInfo(), self, sortDisplayNames)

    #---------------------------------------------------------------#

    def getElements(self):
        """
            Return a list of Element objects in this container.
        """

        #get all the immediate children
        children = self.objectValues()

        #filter the Elements
        return filter(lambda x : x.isInstanceOf("Element"), children)

    #---------------------------------------------------------------#

    def getAcquiredElementsDictionary(self):
        """
            Return a dictionary of Elements, indexed by getId()
            including those in the parent container (recursively).

            NOTE: Like normal Zope Acquisition, local Elements
                  override parent container Elements.
        """

        #is the parent a GenericContainer?
        if isinstance(self.getParent(), GenericContainer):
            result = self.getParent().getAcquiredElementsDictionary()
        else:
            result = {}

        #add the local elements to the result
        for element in self.getElements():
            result.update({element.getId():element})

        return result

    #---------------------------------------------------------------#

    def getAcquiredElementsByTypeDictionary(self):
        """
            Return a dictionary of Elements, indexed by getClassInfo().getClassName()
            including those in the parent container (recursively).

            NOTE: Like normal Zope Acquisition, local Element types
                  override parent container Element types
        """

        #is the parent a GenericContainer?
        if isinstance(self.getParent(), GenericContainer):
            result = self.getParent().getAcquiredElementsByTypeDictionary()
        else:
            result = {}

        #add the local elements to the result
        for element in self.getElements():
            result.update({element.getClassInfo().getClassName():element})

        return result

    #---------------------------------------------------------------#
    
    def getAcquiredZopeObjectsByMetaType(self, metaType):
        """
            Attempt to locate the specified types of zope object
            instance by meta_type.  Return a list of zope objects
        """
        
        #the list of objects (first found listed first)
        result = []
        
        #start search self first
        container = self
        
        #keep searching until we've found all of the required types
        while not container.isTopLevelPrincipiaApplicationObject:
            
            #get the child of the object...
            children = container.objectValues()
        
            #add the zope objects
            result = result + [obj for obj in children if hasattr(obj, 'meta_type') and obj.meta_type == metaType]
            
            #now search the parent
            container = container.aq_parent

        #add the top level container objects
        children = container.objectValues()
        result = result + [obj for obj in children if hasattr(obj, 'meta_type') and obj.meta_type == metaType]
            
        return result

    #---------------------------------------------------------------#

    def isOrderedContainer(self):
        """
            Return if the children of the object may use ordering.

            This is to allow the 'Order' column in the Content
            management tab in Zope
        """

        return false
        
    #---------------------------------------------------------------#

    def getChildrenByOrder(self):
        """
            Returns a list of the orderable children in the container
            in order.
            
            Returns [] if there are no orderable children or the 
            container doesn't support ordering.
        """
        
        #does the container support ordering
        if self.isOrderedContainer():

            #get the orderable children 
            children = [child for child
                        in self.getChildren() 
                        if hasattr(child, 'isOrderable') and child.isOrderable()]
    
            #sort the children into order
            children.sort(lambda x, y : -(x.getOrder() < y.getOrder()))          
            return children                        
        else:
            return []
        
    #---------------------------------------------------------------#

    def getFirstChild(self):
        """
            Return the first orderable child in the container
            Returns None if there is no orderable first child 
            
            PRE: self.isOrderedContainer()
        """
 
        #get the children in order of appearance
        children = self.getChildrenByOrder()
            
        #are there any ordered children
        if len(children) > 0:
            return children[0]
        else:
            return None     
        
    #---------------------------------------------------------------#

    def getLastChild(self):
        """
            Return the last orderable child in the container
            Returns None if there is no orderable last child 
            
            PRE: self.isOrderedContainer()
        """
 
        #get the children in order of appearance
        children = self.getChildrenByOrder()
            
        #are there any ordered children
        if len(children) > 0:
            return children[len(children)-1]
        else:
            return None     
 
    #---------------------------------------------------------------#

    def manage_ObjectMove(self, REQUEST=None, OBJECT=None, DIRECTION=None):
        """
            Attempt to move the specified OBJECT in it's parent
        """
        
        #do we have an object and a direction?
        if OBJECT != None and (DIRECTION == "UP" or DIRECTION == "DOWN"):

            #get the object that we will be moving (the source object)
            srcObject = self[OBJECT]

            #ensure that the source object is orderable
            if srcObject.isOrderable():
                
                #determine the object we are going to be swapping places with (the destination object)
                if DIRECTION == "UP":
                    destObject = srcObject.getPreviousSibling()
                elif DIRECTION == "DOWN":
                    destObject = srcObject.getNextSibling()
                else:
                    destObject = None
            
                #ensure we have a destination
                if destObject != None:
                
                    #swap the previous sibling and this object's order
                    newOrder = destObject.getOrder()
                    oldOrder = srcObject.getOrder()

                    srcObject.setOrder(newOrder)
                    destObject.setOrder(oldOrder)
        
        #redirect back to the management page for this object
        REQUEST.RESPONSE.redirect(REQUEST['URL1'] + '/manage_main')          
        
    #---------------------------------------------------------------#

    def manage_renameObject(self, id, new_id, REQUEST=None):
        """
            Attempt to rename the specified child object
        """
        
        #get the object we are meant to rename
        proposedObject = self._getOb(id)
        
        #is the object a generic object?
        if isinstance(proposedObject, GenericObject):

            #ensure that the new id is ok
            try: 
                self._checkId(new_id)
            except: 
                raise CopyError, MessageDialog(
                      title='Invalid Id',
                      message=sys.exc_info()[1],
                      action ='manage_main')
                      
            #ensure that the object is moveable (as a rename cause a temporary move)
            if not proposedObject.cb_isMoveable():
                raise CopyError, eNotSupported % id            

            #notify the object of the temporary move
            try:    
                proposedObject._notifyOfCopyTo(self, op=1)
            except: raise CopyError, MessageDialog(
                          title='Rename Error',
                          message=sys.exc_info()[1],
                          action ='manage_main')
                          
            #remove the object and add it as a different name                          
            self._delObject(id)
            proposedObject = aq_base(proposedObject)
            proposedObject._setId(new_id)

            #Note - because a rename always keeps the same context, we
            #can just leave the ownership info unchanged.
            self._setObject(new_id, proposedObject, set_owner=0)

            if REQUEST is not None:
                return self.manage_main(self, REQUEST, update_menu=1)
            return None
        
        else:
            #use the super class manage_renameObject
            return ObjectManager.manage_renameObject(self, id, new_id, REQUEST)
            
    #---------------------------------------------------------------#

    def _verifyObjectPaste(self, object, validate_src=true):
        """
            FINAL: Attempt to verify that the proposed paste operation
            ok to proceed with.  
        """

        #set the proposed child & whether we should validate the proposed child
        proposedChild = object
        validateChild = validate_src        

        #determine the type of operation
        isImport = not validate_src
        isCopyOrMove = not isImport
        
        #assume paste is not permitted
        isPastePermitted = false
        
        #does the child have classinfo?
        #(only knowledge engine components are permitted in a knowledge engine tree)
        if not hasattr(aq_base(proposedChild), "getClassInfo"):
            #we are pasting a ZOPE object
            
            #get the meta types for this container as the container may permit zope objects
            zopeMetaTypes = self.getZopeMetaTypes()

            #attempt to locate the type of the proposed child in the zope meta types for this container
            #TODO: this should also check the children of the proposed child, as they too may not be permitted in this container
            isPastePermitted = false 
            for meta in zopeMetaTypes:
                if meta['name'] == proposedChild.meta_type:
                    isPastePermitted = true
                    break

            #is the paste permitted?
            if not isPastePermitted:        
                raise CopyError, MessageDialog(title='Invalid Paste Operation',
                                               message='Cannot paste non-Knowledge Engine objects into %s %s.' % (self.getClassInfo().getClassIconHTML(), self.getTitleOrId()),
                                               action='manage_main')            

        else:
            #does this container permit this proposedChild as a child?
            if not self.getClassInfo().isChildPermitted(proposedChild.getClassInfo(), proposedChild):           
                raise CopyError, MessageDialog(title='Invalid Operation',
                                               message='%s %s cannot exist in %s %s.' % (proposedChild.getClassInfo().getClassIconHTML(), proposedChild.getTitleOrId(), self.getClassInfo().getClassIconHTML(), self.getTitleOrId()),
                                               action='manage_main')            

            #does the proposed child think it is allowed in this container?
            if not proposedChild.getClassInfo().isPermittedInContainer(self.getClassInfo(), self, proposedChild):        
                raise CopyError, MessageDialog(title='Invalid Operation',
                                               message='%s %s cannot exist in %s %s.' % (proposedChild.getClassInfo().getClassIconHTML(), proposedChild.getTitleOrId(), self.getClassInfo().getClassIconHTML(), self.getTitleOrId()),
                                               action='manage_main')            

            #paste is permitted
            isPastePermitted = true 
            
        #TODO: check security permission for the user
        
        #otherwise paste operation is OK!
        return isPastePermitted
    
    #---------------------------------------------------------------#

    def manage_delObjects(self, ids=[], REQUEST=None):
        """
            Delete a subordinate object
            The objects specified in 'ids' get deleted.
        """
        
        #get the objects we are about to delete
        objectsToDelete = [self[id] for id in ids]
        
        #are any of the objects to be deleted Events 
        #(we need to redirect to the appropriate tab)
        nrEvents = len([obj for obj in objectsToDelete if hasattr(obj,'isInstanceOf') and obj.isInstanceOf('Event')])
                
        #delegate the deletion to the super class
        result = ObjectManager.manage_delObjects(self, ids, None)        

        #was a result provided (might be an error)
        if result is None:
            if REQUEST is not None and hasattr(REQUEST, 'URL1'):
                try: 
                    url = self.DestinationURL()
                except: 
                    url = REQUEST['URL1']        
                    
                if nrEvents > 0:
                    REQUEST.RESPONSE.redirect(url + '/eventEditor')          
                else:
                    REQUEST.RESPONSE.redirect(url + '/manage_workspace')          
                    
        else:
            return result
        
    #---------------------------------------------------------------#

#####################################################################
## Class Initialisation
# (none)
